package sma.nasa;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JPanel;

/* ******************************************************************************************
 CCC   OOO  N   N  SSS  TTTTT RRRR   AAA  III N   N TTTTT  SSS   CCC  H   H EEEEE  CCC  K   K EEEEE RRRR  
 C     O   O NN  N S       T   R   R A   A  I  NN  N   T   S     C     H   H E     C     K  K  E     R   R 
 C     O   O N N N  SSS    T   RRRR  AAAAA  I  N N N   T    SSS  C     HHHHH EEE   C     KK    EEE   RRRR  
 C     O   O N  NN     S   T   R R   A   A  I  N  NN   T       S C     H   H E     C     K K   E     R R   
 CCC   OOO  N   N  SSS    T   R  R  A   A III N   N   T    SSS   CCC  H   H EEEEE  CCC  K  K  EEEEE R  R  
 ****************************************************************************************** */

//=============================================
public class ConstraintsChecker {
	// =============================================
	// Minimum and maximum possible beta angles, in degrees.
	static final double MIN_BETA = -90.0;
	static final double MAX_BETA = 90.0;

	// Minimum and maximum valid yaw angles, in degrees.
	static final double MIN_YAW = 0.0;
	final double MAX_YAW = 7.0;

	// Number of intermediate evaluation points along a single orbit.
	static final int NUM_STATES = 92;

	// Solar array names
	static final String[] SOLAR_ARRAY_NAME = new String[] { "1A", "2A", "3A", "4A", "1B", "2B", "3B", "4B" };

	// SARJ axe names
	static final String[] SARJ_NAME = new String[] { "SSARJ", "PSARJ" };

	// Time (in seconds) between two consecutive evaluation points.
	static final double TIME_PER_STATE = 60.0;

	// Maximum BGA/SARJ rotation speed, in degrees per second.
	public static final double MAX_BGA_SPEED = 0.25;
	public static final double MAX_SARJ_SPEED = 0.15;

	// Maximum BGA/SARJ acceleration, in degrees per second^2.
	public static final double MAX_BGA_ACC = 0.01;
	public static final double MAX_SARJ_ACC = 0.005;

	// Number of strings and longerons in a single solar array.
	static final int NUM_STRINGS = 41 * 2;
	static final int NUM_LONGERONS = 4;

	// Minimum shadow fraction at which a longeron is considered shadowed.
	static final double MIN_LONGERON_SHADOW = 0.1;

	// Minimum value of a counter which is considered to be a failure.
	static final double MIN_FAILURE_HEAT = 21;

	// The multiplier from the power formula, in watt per meter^2.
	static final double POWER_MULTIPLIER = 1371.3 * 0.1;

	// String area that generates power, in meter^2. Currently assumed
	// to be 400 cells of 8 cm x 8 cm dimensions.
	static final double STRING_AREA = 400 * 0.08 * 0.08;

	// below is the order of constraints for 1A 2A 3A... ordering
	public static final double[] MIN_POWER_REQ = new double[] { 6300.0, 6400.0, 6700.0, 8900.0, 5500.0, 8100.0, 4400.0, 4300.0 };

	// tolerance
	public static final double EPS = 1e-9;

	// end of constants

	java.util.List<String> errors = new ArrayList<String>();
	java.util.List<String> lowPowerBGAs = new ArrayList<String>();

	double beta, yaw;

	double[][] angleSARJ = new double[NUM_STATES][2];
	double[][] speedSARJ = new double[NUM_STATES][2];
	double[][] angleBGA = new double[NUM_STATES][8];
	double[][] speedBGA = new double[NUM_STATES][8];

	double[][] rotSARJ = new double[NUM_STATES][2];
	double[][] rotBGA = new double[NUM_STATES][8];

	double[][] cosTheta = new double[NUM_STATES][8];
	double[][][] stringShadow = new double[NUM_STATES][8][NUM_STRINGS];
	double[][][] longeronShadow = new double[NUM_STATES][8][NUM_LONGERONS];

	double[][] power = new double[NUM_STATES][8];
	double[] totalPower = new double[8];
	int[] maxCounter = new int[8];

	double[] lastAns = null; // return value from last evaluateSingleState call

	// set beta angle, no checks
	// =============================================
	void setBeta(double beta)
	// =============================================
	{
		if (beta < MIN_BETA || beta > MAX_BETA) {
			errors.add("The value of beta angle must be between " + MIN_BETA + " and " + MAX_BETA + " degrees, inclusive.");
		}
		this.beta = beta;
	}

	// set yaw angle, check validity
	// =============================================
	void setYaw(double yaw)
	// =============================================
	{
		if (Double.isNaN(yaw)) {
			errors.add("The value of yaw angle must not be NaN.");
		}
		if (yaw < MIN_YAW || yaw > MAX_YAW) {
			errors.add("The value of yaw angle must be between " + MIN_YAW + " and " + MAX_YAW + " degrees, inclusive.");
		}

		this.yaw = yaw;
	}

	// Sets data (angles, speeds) for minute = t (0 <= t < NUM_STATES).
	// Makes trivial checks.
	// =============================================
	void setDataForFrame(int t, double[] data)
	// =============================================
	{
		if (data.length != 20) {
			errors.add("Return value from getStateAtMinute must " + "contain exactly 20 elements. Minute = " + t + ".");
			return;
		}

		for (int i = 0; i < data.length; i++)
			if (Double.isNaN(data[i])) {
				errors.add("NaN elements in return value are not " + "allowed. Minute = " + t + ". Position (0-based) = " + i + ".");
				return;
			}

		angleSARJ[t][0] = data[0];
		angleSARJ[t][1] = data[2];
		speedSARJ[t][0] = data[1];
		speedSARJ[t][1] = data[3];
		for (int i = 4, j = 0; j < 8; j++, i += 2) {
			angleBGA[t][j] = data[i];
			speedBGA[t][j] = data[i + 1];
		}

		for (int i = 0; i < angleSARJ[t].length; i++)
			if (angleSARJ[t][i] < 0.0 || angleSARJ[t][i] >= 360.0) {
				errors.add("Each SARJ angle needs to be from 0.0 " + "(inclusive) to 360.0 (exclusive). Minute = " + t + ". SARJ = "
						+ SARJ_NAME[i] + ". Your angle = " + angleSARJ[t][i] + ".");
			}

		for (int i = 0; i < angleBGA[t].length; i++)
			if (angleBGA[t][i] < 0.0 || angleBGA[t][i] >= 360.0) {
				errors.add("Each BGA angle needs to be from 0.0 " + "(inclusive) to 360.0 (exclusive). Minute = " + t + ". BGA = "
						+ SOLAR_ARRAY_NAME[i] + ". Your angle = " + angleBGA[t][i] + ".");
			}

		for (int i = 0; i < speedSARJ[t].length; i++)
			if (Math.abs(speedSARJ[t][i]) > MAX_SARJ_SPEED) {
				errors.add("The absolute value of SARJ rotation speed " + "can't exceed " + MAX_SARJ_SPEED + ". Minute = " + t
						+ ". SARJ = " + SARJ_NAME[i] + ". Your speed = " + speedSARJ[t][i] + ".");
			}

		for (int i = 0; i < speedBGA[t].length; i++)
			if (Math.abs(speedBGA[t][i]) > MAX_BGA_SPEED) {
				errors.add("The absolute value of BGA rotation speed " + " can't exceed " + MAX_BGA_SPEED + ". Minute = " + t + ". BGA = "
						+ SOLAR_ARRAY_NAME[i] + ". Your speed = " + speedBGA[t][i] + ".");
			}
	}

	// Checks that angles and speeds are consistent.
	// =============================================
	void checkAnglesAndSpeeds()
	// =============================================
	{
		for (int t = 0; t < NUM_STATES; t++) {
			int tt = (t + 1) % NUM_STATES;
			for (int i = 0; i < 2; i++) {
				double[] ret = processTransition(angleSARJ[t][i], speedSARJ[t][i], angleSARJ[tt][i], speedSARJ[tt][i], MAX_SARJ_SPEED,
						MAX_SARJ_ACC);
				if (ret[0] != 1.0) {
					errors.add("It is impossible to make a transition between" + " your SARJ configurations from minute " + t
							+ " to minute " + tt + ". SARJ = " + SARJ_NAME[i] + ".");
				}
				rotSARJ[t][i] = ret[1];
			}

			for (int i = 0; i < 8; i++) {
				double[] ret = processTransition(angleBGA[t][i], speedBGA[t][i], angleBGA[tt][i], speedBGA[tt][i], MAX_BGA_SPEED,
						MAX_BGA_ACC);
				if (ret[0] != 1.0) {
					errors.add("It is impossible to make a transition" + " between your BGA configurations from minute " + t
							+ " to minute " + tt + ". BGA = " + SOLAR_ARRAY_NAME[i] + ".");
				}
				rotBGA[t][i] = ret[1];
			}
		}
	}

	// Evaluates a particular frame.
	// =============================================
	void evaluateFrame(int t)
	// =============================================
	{
		evaluateFrame(t, t * 360.0 / NUM_STATES);
	}

	// =============================================
	void evaluateFrame(int t, double alpha)
	// =============================================
	{
		double[] inp = new double[] { t, alpha, beta, yaw, angleSARJ[t][0], angleSARJ[t][1], angleBGA[t][0], angleBGA[t][1],
				angleBGA[t][2], angleBGA[t][3], angleBGA[t][4], angleBGA[t][5], angleBGA[t][6], angleBGA[t][7] };

		// lt.addFatalError ( "about to call evaluateSingleState\n" ) ;
		// =============================================
		double[] out = evaluateSingleState(inp);
		// =============================================
		// lt.addFatalError ( "returned from evaluateSingleState\n" ) ;
		if (out == null) {
			errors.add("returned from evaluateSingleState, " + "Error: result = null\n");
			return;
		}

		int pos = 0;

		for (int i = 0; i < 8; i++) {
			cosTheta[t][i] = out[pos++];
			// if ( cosTheta [t][i] <= 0 )
			// lt.addFatalError ( "Error SAW " + i + " cosine " +
			// cosTheta[t][i] + "\n" ) ;
			// if ( t == 0 )
			// lt.addFatalError ( "SAW " + i + " cosine " +
			// cosTheta[t][i] + "\n" ) ;
		}

		for (int i = 0; i < 8; i++)
			for (int j = 0; j < NUM_STRINGS; j++) {
				stringShadow[t][i][j] = out[pos++];
				if (stringShadow[t][i][j] < 0 || stringShadow[t][i][j] > 1) {
					// lt.addFatalError ( "Error: SAW " + i + " string " + j +
					// " shadow " + stringShadow[t][i][j] ) ;
				}
			}

		for (int i = 0; i < 8; i++)
			for (int j = 0; j < NUM_LONGERONS; j++)
				longeronShadow[t][i][j] = out[pos++];
	}

	// Checks whether any longerons failed.
	// =============================================
	int[] checkLongerons()
	// =============================================
	{
		for (int id = 0; id < 8; id++) {
			int[] add = new int[NUM_STATES];
			for (int t = 0; t < NUM_STATES; t++) {
				int shadowCnt = 0;
				for (int i = 0; i < NUM_LONGERONS; i++)
					if (longeronShadow[t][id][i] >= MIN_LONGERON_SHADOW)
						shadowCnt++;

				add[t] = (shadowCnt % 2 == 1 ? 1 : -1);
			}

			int counter = 0;
			// simulate 100 rotations around the orbit, since it works quick anyway
			boolean ok = true;
			for (int x = 0; x < 100; x++) {
				for (int t = 0; t < NUM_STATES; t++) {
					counter += add[t];
					if (counter < 0)
						counter = 0;
					maxCounter[id] = Math.max(maxCounter[id], counter);
					if (counter >= MIN_FAILURE_HEAT) {
						if (ok)
							errors.add("Longerons of solar array " + SOLAR_ARRAY_NAME[id] + " failed. Minute = " + (t + x * NUM_STATES)
									+ ".");
						ok = false;
					}
				}
			}
		}
		return maxCounter;
	}

	// Evaluates power for frame t.
	// =============================================
	void evaluatePower(int t)
	// =============================================
	{
		for (int id = 0; id < 8; id++) {
			power[t][id] = 0;
			for (int i = 0; i < NUM_STRINGS; i++) {
				double cosFactor = Math.max(0.0, cosTheta[t][id]);
				power[t][id] += cosFactor * fractionToFactor(stringShadow[t][id][i]);
			}

			power[t][id] *= POWER_MULTIPLIER * STRING_AREA;
			// ISS.longtest.addFatalError ( power[t][id] + " " ) ;
		}
		// ISS.longtest.addFatalError ( "\n" ) ;
	}

	// Aggregates all powers. Checks minimum power requirements.
	// =============================================
	void aggregateAndCheckPower()
	// =============================================
	{
		for (int id = 0; id < 8; id++) {
			totalPower[id] = 0;
			for (int t = 0; t < NUM_STATES; t++) {
				totalPower[id] += power[t][id];
			}

			totalPower[id] /= NUM_STATES;

			if (totalPower[id] < MIN_POWER_REQ[id]) {
				lowPowerBGAs.add(SOLAR_ARRAY_NAME[id]);
			}
		}
	}

	// Verifies whether a transition from (angle1, speed1) to
	// (angle2, speed2) is possible within TIME_PER_STATE seconds
	// assuming |speed| <= maxSpeed and |acceleration| <= maxAcc.
	// If it is possible, element 0 of return will be 1.0, otherwise 0.0.
	// Element 1 of return will contain the minimum amount of rotation
	// necessary to make such transition.
	// (This is only defined if transition is possible.)
	// =============================================
	double[] processTransition(double angle1, double speed1, double angle2, double speed2, double maxSpeed, double maxAcc)
	// =============================================
	{
		// make sure speed1 >= 0, to reduce the number of cases
		if (speed1 < 0)
			return processTransition(-angle1, -speed1, -angle2, -speed2, maxSpeed, maxAcc);

		// determine angular shift we need to make
		double shift = angle2 - angle1;
		if (shift < -180.0)
			shift += 360.0;
		if (shift > 180.0)
			shift -= 360.0;

		// check validity
		double[] res = new double[] { 0.0, 0.0 };

		if (!canMakeTransition(shift, speed1, speed2, -maxSpeed, maxSpeed, maxAcc))
			return res;

		res[0] = 1.0;
		res[1] = Math.abs(shift);

		// check if some extra rotation is needed
		double pos, neg;
		if (speed2 >= 0) {
			// can we always keep speed positive?
			if (canMakeTransition(shift, speed1, speed2, 0, maxSpeed, maxAcc))
				return res;

			// minimize rotation at positive speeds
			double t1 = speed1 / maxAcc;
			double t2 = speed2 / maxAcc;
			pos = path(t1, 0, maxAcc) + path(t2, 0, maxAcc);
			neg = pos - shift;
		} else {
			double t1 = speed1 / maxAcc;
			double t2 = (-speed2) / maxAcc;
			pos = path(t1, 0, maxAcc);
			neg = path(t2, 0, maxAcc);
			double midShift = pos - neg;
			if (shift < midShift) {
				// minimize rotation at positive speeds
				neg = pos - shift;
			} else {
				// minimize rotation at negative speeds
				pos = neg + shift;
			}
		}

		res[1] = pos + neg;

		return res;
	}

	// Verifies whether it's possible to make an angular shift of "shift"
	// within TIME_PER_STATE seconds
	// if initial speed is "speed1", final speed is "speed2",
	// minSpeed <= speed <= maxSpeed and |acceleration| <= maxAcc.
	// =============================================
	boolean canMakeTransition(double shift, double speed1, double speed2, double minSpeed, double maxSpeed, double maxAcc)
	// =============================================
	{
		// simple acceleration check
		if (Math.abs((speed2 - speed1) / TIME_PER_STATE) > maxAcc + EPS)
			return false;

		double minShift = 0.0, maxShift = 0.0;

		// find minimum possible angular shift
		double t1 = (speed1 - minSpeed) / maxAcc;
		double t2 = TIME_PER_STATE - (speed2 - minSpeed) / maxAcc;
		if (t1 <= t2) {
			minShift += path(t1, speed1, -maxAcc);
			minShift += path(t2 - t1, minSpeed, 0);
			minShift += path(TIME_PER_STATE - t2, minSpeed, maxAcc);
		} else {
			double t = (speed1 - speed2 + TIME_PER_STATE * maxAcc) / 2.0 / maxAcc;
			minShift += path(t, speed1, -maxAcc);
			minShift += path(TIME_PER_STATE - t, speed1 - maxAcc * t, maxAcc);
		}

		// find maximum possible angular shift
		t1 = (maxSpeed - speed1) / maxAcc;
		t2 = TIME_PER_STATE - (maxSpeed - speed2) / maxAcc;
		if (t1 <= t2) {
			maxShift += path(t1, speed1, maxAcc);
			maxShift += path(t2 - t1, maxSpeed, 0);
			maxShift += path(TIME_PER_STATE - t2, maxSpeed, -maxAcc);
		} else {
			double t = (speed2 - speed1 + TIME_PER_STATE * maxAcc) / 2.0 / maxAcc;
			maxShift += path(t, speed1, maxAcc);
			maxShift += path(TIME_PER_STATE - t, speed1 + maxAcc * t, -maxAcc);
		}

		// validate
		return (minShift <= shift && shift <= maxShift + EPS);
	}

	// Returns path, given time, initial speed and acceleration.
	// =============================================
	double path(double t, double v0, double a)
	// =============================================
	{
		return v0 * t + a * t * t / 2.0;
	}

	// Inputs:
	// - Frame ID. 0-based ID of the frame.
	// - Alpha angle. This and all subsequent angles are in degrees.
	// Thus at time t, 0 <= t < NUM_STATES, alpha angle is
	// equal to t / NUM_STATES * 360.0.
	// - Beta angle.
	// - Yaw angle.
	// - 2 SARJ angles.
	// - 8 BGA angles (in order: 1A, 2A, 3A, 4A, 1B, 2B, 3B, 4B.
	// Outputs:
	// - 8 cos(theta) values used for power calculation
	//
	// - NUM_STRINGSx8 shadow fractions (from 0.0 to 1.0) of individual strings
	// (first NUM_STRINGS values for 1A, then for 2A, then for 3A and so on).
	//
	// - NUM_LONGERONSx8 shadow fractions (from 0.0 to 1.0) of individual
	// longerons (first NUM_LONGERONS values for 1A, then for 2A, then for 3A and so on).
	// =============================================
	public double[] evaluateSingleState(double[] input)
	// =============================================
	{
		double minute = input[0];
		ISSVis.alpha = input[1];
		ISSVis.beta = input[2];
		ISSVis.yaw = input[3];

		for (int i = 0; i < 10; i++)
			ISSVis.control[i] = input[i + 4];

		// set longerons invisible
		ISSVis.longeron_visibility(ISSVis.m, false);

		// set up position of sun
		ISSVis.sunTransform = ISSVis.makeSunProjection(ISSVis.beta, ISSVis.alpha);
		ISSVis.inverse = ISSVis.makeInverseSunProjection(ISSVis.beta, ISSVis.alpha);
		ISSVis.toSun = new V();
		ISSVis.sunTransform.transform(new V(0, -1, 0), ISSVis.toSun);

		// rotate joints
		ISSVis.rotate_SARJ_BGA(ISSVis.m, ISSVis.control);
		ISSVis.m.transform();

		// go

		double[] answer = ISSVis.calculateOnePosition(ISSVis.m, ISSVis.toSun, ISSVis.inverse);

		if (minute < -1) {
			for (int saw = 0; saw < 8; saw++) {
				double p = 0;
				for (int str = 0; str < 82; str++) {
					p += answer[saw] * 104.96 / 41 * 1371.3 * 0.1 * Math.max(0.0, 1.0 - answer[8 + saw * 82 + str] * 5);
				}
				// p /= 82 ;
			}
		}

		this.lastAns = answer;

		return answer;
	}

	// Convert shadow fraction to shadow factor.
	// =============================================
	double fractionToFactor(double x)
	// =============================================
	{
		return Math.max(0.0, 1.0 - 5.0 * x);
	}

	// =============================================
	public double totalRotation(int b)
	// =============================================
	{
		double totalRotation = 0;
		for (int j = 0; j < 92; j++)
			totalRotation += rotBGA[j][b];
		return totalRotation;

	}

	// =============================================
	public double rawScore()
	// =============================================
	{
		double power = 0;
		for (int i = 0; i < 8; i++)
			power += totalPower[i];
		double maxBGArotation = 0;
		for (int i = 0; i < 8; i++) {
			maxBGArotation = Math.max(maxBGArotation, totalRotation(i));
		}

		double score = power * Math.min(1.0, Math.pow(2.0, (80.0 - maxBGArotation) / 300));
		for (int i = 0; i < lowPowerBGAs.size(); i++) {
			score /= 2.0;
		}
		return score;
	}
	// =============================================
	// *********************************************
} // end class ConstraintsChecker
// *********************************************

/* *********************************************
 * DDDD RRRR AAA W W EEEEE RRRR D D R R A A W W E R R D D RRRR AAAAA W W W EEE RRRR D D R R A A WW WW E R R DDDD R R A A
 * W W EEEEE R R*********************************************
 */

// *********************************************
class Drawer extends JFrame
// *********************************************
{

	/*
	 * DDDD RRRR AAA W W EEEEE RRRR K K EEEEE Y Y L III SSS TTTTT EEEEE N N TTTTT EEEEE RRRR D D R R A A W W E R R K K E
	 * Y Y L I S T E NN N T E R R D D RRRR AAAAA W W W EEE RRRR KK EEE Y L I SSS T EEE N N N T EEE RRRR D D R R A A WW
	 * WW E R R K K E Y L I S T E N NN T E R R DDDD R R A A W W EEEEE R R K K EEEEE Y LLLLL III SSS T EEEEE N N T EEEEE
	 * R R
	 */
	// *********************************************
	private class DrawerKeyListener extends KeyAdapter
	// *********************************************
	{
		// *********************************************
		public void keyPressed(KeyEvent e)
		// *********************************************
		{
			if (e.getKeyChar() == 'q' || e.getKeyChar() == '' || e.getKeyChar() == 'k' || e.getKeyChar() == 'p') {
				System.exit(0);
			}
			if (e.getKeyChar() == ' ') {
				synchronized (paintMutex) {
					animationMode = !animationMode;
					panel.repaint();
				}
			}
			if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyChar() == 's' || e.getKeyChar() == 'e') {
				synchronized (paintMutex) {
					animationMode = false;
					curFrame = (curFrame - 1 + frameCnt) % frameCnt;
					panel.repaint();
				}
			}
			if (e.getKeyCode() == KeyEvent.VK_RIGHT || e.getKeyCode() == KeyEvent.VK_UP || e.getKeyChar() == 'f' || e.getKeyChar() == 'd')

			{
				synchronized (paintMutex) {
					animationMode = false;
					curFrame = (curFrame + 1) % frameCnt;
					panel.repaint();
				}
			}
		}
	}

	/*
	 * DDDD RRRR AAA W W EEEEE RRRR PPPP AAA N N EEEEE L D D R R A A W W E R R P P A A NN N E L D D RRRR AAAAA W W W EEE
	 * RRRR PPPP AAAAA N N N EEE L D D R R A A WW WW E R R P A A N NN E L DDDD R R A A W W EEEEE R R P A A N N EEEEE
	 * LLLLL
	 */
	// *********************************************
	private class DrawerPanel extends JPanel
	// *********************************************
	{
		public void paint(Graphics g) {
			synchronized (paintMutex) {
				if (curFrame < 0 || curFrame >= frameCnt) {
					return;
				}

				BufferedImage bi = new BufferedImage(xz, yz, BufferedImage.TYPE_INT_RGB);

				byte[][] pix = movie[curFrame];
				for (int y = 0; y < yz; ++y)
					for (int x = 0; x < xz * 3; x += 3) {
						int p = (((int) pix[y][x]) & 255) * 65536;
						p += (((int) pix[y][x + 1]) & 255) * 256;
						p += (((int) pix[y][x + 2]) & 255);

						bi.setRGB(x / 3, y, p);
					}

				g.drawImage(bi, 0, 0, null);

				g.setFont(new Font("Arial", Font.BOLD, 12));

				int s = 0;
				int w = 37;
				int st = 58;
				g.setColor(new Color(250, 0, 0));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(150, 250, 250));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(250, 100, 100));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(0, 250, 250));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);

				g.setColor(new Color(230, 230, 150));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(0, 0, 250));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(230, 230, 0));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);
				g.setColor(new Color(100, 100, 250));
				g.fillRect(xz / 2 + st + (s++) * w, 5, w, 20);

				g.setColor(Color.BLACK);
				for (int line = 0; line < titles[curFrame].length - 1; ++line) {
					g.drawChars(titles[curFrame][line].toCharArray(), 0, titles[curFrame][line].length(), xz / 2, 20 + 20 * line);
				}
				if (titles[curFrame].length > 0)
					g.drawChars(titles[curFrame][titles[curFrame].length - 1].toCharArray(), 0,
							titles[curFrame][titles[curFrame].length - 1].length(), xz / 2, yz - 11);

				if (footer != null) {
					for (int line = 0; line < footer.length; ++line) {
						g.drawChars(footer[line].toCharArray(), 0, footer[line].length(), xz / 2, yz - (31 + 20 * line));
					}
				}

				// s = "Animation mode: " + (animationMode ? "on" : "off");
				// g.drawChars(s.toCharArray(), 0, s.length(), xz / 2, 40);

				drawer.setTitle(windowTitles[curFrame]);
			}
		}
		// *********************************************
	} // class DrawerPanel
		// *********************************************

	byte[][][] movie = new byte[92][][];
	String[][] titles = new String[92][0];
	String[] windowTitles = new String[92];
	String[] footer = null;
	public int frameCnt = 0, curFrame = -1;

	private int xz, yz;

	public boolean animationMode = true;

	private static Drawer drawer;
	private static DrawerPanel panel;

	// *********************************************
	private Drawer()
	// *********************************************
	{
		super();

		panel = new DrawerPanel();
		getContentPane().add(panel);

		addKeyListener(new DrawerKeyListener());

		xz = 2 * ISSVis.res;
		yz = ISSVis.res;

		setSize(xz + 5, yz + 30);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
	}

	public static Object paintMutex = new Object();

	// *********************************************
	public void setFrame(int frameId, byte[][] data, String[] title, String[] foot, String windowTitle)
	// *********************************************
	{
		synchronized (paintMutex) {
			titles[frameId] = new String[title.length];
			if (foot != null && foot.length > 0)
				footer = foot.clone();
			for (int line = 0; line < title.length; ++line)
				titles[frameId][line] = title[line];
			drawer.movie[frameId] = new byte[data.length][];
			for (int i = 0; i < data.length; i++) {
				drawer.movie[frameId][i] = data[i].clone();
			}
			drawer.windowTitles[frameId] = windowTitle;
			drawer.frameCnt = frameId + 1;
			drawer.curFrame = frameId;
			panel.repaint();
		}
	}

	// *********************************************
	public void displayFrame(int frameId)
	// *********************************************
	{
		synchronized (paintMutex) {
			drawer.curFrame = frameId;
			panel.repaint();
		}
	}

	// *********************************************
	public static Drawer getDrawer()
	// *********************************************
	{
		if (drawer == null) {
			drawer = new Drawer();
		}
		return drawer;
	}
	// ************************************************************
} // end class Drawer
// ************************************************************

/* ************************************************************
 * BBBB III B B I BBBB I B B I BBBB III***********************************************************
 */

// ************************************************************
class Bi {
	// ************************************************************
	final BufferedImage bi;

	// *********************************************
	Bi()
	// *********************************************
	{
		bi = null;
	};

	// *********************************************
	Bi(int res)
	// *********************************************
	{
		bi = new BufferedImage(res + res, res, BufferedImage.TYPE_INT_RGB);
	}
	// *********************************************
}; // class Bi
// ************************************************************

/* ************************************************************
 * M M MM MM M M M M M M M***********************************************************
 */
// ************************************************************
class M
// ************************************************************

// ************************************************************
{
	double[][] m; // the current transformation of this instance
	static M tempMc; // compose temporary matrix
	static M tempMr; // rotate and translate temporary matrix
						// must be different than compose temp matrix
	static double[] tempA1; // two temps for Matrix Vector multiply
	static double[] tempA2;

	void print(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post);
	}

	void print(String s) {
		print();
		System.out.print(s);
	}

	void println(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post + "\n");
	}

	void println(String s) {
		print();
		System.out.print(s + "\n");
	}

	void println() {
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++)
				System.out.print(" " + m[i][j]);
			System.out.println();
		}
	}

	void print() {
		System.out.println();
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++)
				System.out.print(" " + m[i][j]);
			System.out.println();
		}
	}

	void transform(V in, V out) {
		tempA1[0] = in.x;
		tempA1[1] = in.y;
		tempA1[2] = in.z;
		tempA1[3] = 1;
		for (int i = 0; i < 4; ++i) {
			tempA2[i] = 0;
			for (int j = 0; j < 4; ++j)
				tempA2[i] += m[i][j] * tempA1[j];
		}
		out.x = tempA2[0];
		out.y = tempA2[1];
		out.z = tempA2[2];
	};

	void transform(P in, P out) {
		// print ( ) ;
		tempA1[0] = in.x;
		tempA1[1] = in.y;
		tempA1[2] = in.z;
		tempA1[3] = 1;
		for (int i = 0; i < 4; ++i) {
			tempA2[i] = 0;
			for (int j = 0; j < 4; ++j)
				tempA2[i] += m[i][j] * tempA1[j];
		}
		out.x = tempA2[0];
		out.y = tempA2[1];
		out.z = tempA2[2];
		// in.print ( ) ;
		// out.println ( ) ;
	};

	void compose_pre(M pre, M out) {
		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j) {
				tempMc.m[i][j] = 0;
				for (int k = 0; k < 4; ++k)
					tempMc.m[i][j] += pre.m[i][k] * m[k][j];
			}
		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = tempMc.m[i][j];
	};

	void compose_post(M post, M out) {
		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j) {
				tempMc.m[i][j] = 0;
				for (int k = 0; k < 4; ++k)
					tempMc.m[i][j] += m[i][k] * post.m[k][j];
			}
		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = tempMc.m[i][j];
	};

	static void translate(V trans, M out) {
		translate(trans.x, trans.y, trans.z, out);
	};

	static void translate(double x, double y, double z, M out) {
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = 0;
			out.m[i][i] = 1;
		}
		out.m[0][3] = x;
		out.m[1][3] = y;
		out.m[2][3] = z;
	};

	static void negative_translate(V trans, M out) {
		M.translate(-trans.x, -trans.y, -trans.z, out);
	};

	void translate_compose(double x, double y, double z, M out) {
		M.translate(x, y, z, tempMr);
		compose_pre(tempMr, out);
	};

	void translate_compose(V trans, M out) {
		M.translate(trans, tempMr);
		compose_pre(tempMr, out);
	};

	void rotate_x_axis_compose(double angle, M out) {
		M.rotate_x_axis(angle, tempMr);
		compose_pre(tempMr, out);
	};

	void rotate_y_axis_compose(double angle, M out) {
		M.rotate_y_axis(angle, tempMr);
		compose_pre(tempMr, out);
	};

	void rotate_z_axis_compose(double angle, M out) {
		M.rotate_z_axis(angle, tempMr);
		compose_pre(tempMr, out);
	};

	static void rotate_x_axis(double angle, M out) {
		double ang = angle / 180 * Math.PI;

		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = 0;
		out.m[1][1] = out.m[2][2] = Math.cos(ang);
		out.m[2][1] = Math.sin(ang);
		out.m[1][2] = -out.m[2][1];
		out.m[0][0] = 1;
		out.m[3][3] = 1;
	};

	static void rotate_y_axis(double angle, M out) {
		double ang = angle / 180 * Math.PI;

		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = 0;
		out.m[0][0] = out.m[2][2] = Math.cos(ang);
		out.m[2][0] = Math.sin(ang);
		out.m[0][2] = -out.m[2][0];
		out.m[1][1] = 1;
		out.m[3][3] = 1;
	};

	static void rotate_z_axis(double angle, M out) {
		double ang = angle / 180 * Math.PI;

		for (int i = 0; i < 4; ++i)
			for (int j = 0; j < 4; ++j)
				out.m[i][j] = 0;
		out.m[0][0] = out.m[1][1] = Math.cos(ang);
		out.m[0][1] = Math.sin(ang);
		out.m[1][0] = -out.m[0][1];
		out.m[2][2] = 1;
		out.m[3][3] = 1;
	};

	M() {
		if (tempMr == null) {
			// System.out.println ( "intializing matrix static fields" ) ;
			tempMr = new M(1);
			tempMc = new M(1);
			tempA1 = new double[4];
			tempA2 = new double[4];
		}
		m = new double[4][4];
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j)
				m[i][j] = 0;
			m[i][i] = 1;
		}
	};

	M(M orig) {
		if (tempMr == null) {
			// System.out.println ( "intializing matrix static fields" ) ;
			tempMr = new M(1);
			tempMc = new M(1);
			tempA1 = new double[4];
			tempA2 = new double[4];
		}
		m = new double[4][4];
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j)
				m[i][j] = orig.m[i][j];
		}
	};

	M(int flag) {
		// System.out.println ( "M ( 1 )" ) ;
		m = new double[4][4];
		for (int i = 0; i < 4; ++i) {
			for (int j = 0; j < 4; ++j)
				m[i][j] = 0;
			m[i][i] = 1;
		}
	};
	// ************************************************************
}; // end of class M
// ************************************************************

/* ************************************************************
 * V V V V V V V V V***********************************************************
 */

// ************************************************************
class V
// ************************************************************
// ************************************************************
{
	double x, y, z;

	void print(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post);
	}

	void print(String s) {
		print();
		System.out.print(s);
	}

	void println(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post + "\n");
	}

	void println(String s) {
		print();
		System.out.print(s + "\n");
	}

	void print() {
		System.out.print("(" + x + ", " + y + ", " + z + ")");
	}

	void println() {
		print();
		System.out.println();
	};

	double dotp(P v) {
		return x * v.x + y * v.y + z * v.z;
	};

	double dot(V v) {
		return x * v.x + y * v.y + z * v.z;
	};

	V(double x1, double x2, double x3) {
		x = x1;
		y = x2;
		z = x3;
	}

	V(V o) {
		x = o.x;
		y = o.y;
		z = o.z;
	}

	V() {
		x = 0;
		y = 0;
		z = 0;
	}
	// ************************************************************
} // end class V
// ************************************************************

/* ************************************************************
 * PPPP P P PPPP P P***********************************************************
 */

// ************************************************************
class P
// ************************************************************
// ************************************************************
{
	double x, y, z, w;

	void print(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post);
	}

	void print(String s) {
		print();
		System.out.print(s);
	}

	void println(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post + "\n");
	}

	void println(String s) {
		print();
		System.out.print(s + "\n");
	}

	void print() {
		System.out.print("(" + x + ", " + y + ", " + z + ")");
	}

	void println() {
		print();
		System.out.println();
	};

	double dist2(P a) {
		return ((x - a.x) * (x - a.x) + (y - a.y) * (y - a.y) + (z - a.z) * (z - a.z));
	}

	double dist(P a) {
		return Math.sqrt((x - a.x) * (x - a.x) + (y - a.y) * (y - a.y) + (z - a.z) * (z - a.z));
	}

	P() {
		w = 1;
	};

	P(double x, double y, double z) {
		this.x = x;
		this.y = y;
		this.z = z;
		this.w = 1;
	};

	P(P o) {
		x = o.x;
		y = o.y;
		z = o.z;
		w = o.w;
	};

	// ************************************************************
}; // end of class P
// ************************************************************

/************************************************************
 * BBBB BBBB OOO X X B B B B O O X X BBBB BBBB O O X B B B B O O X X BBBB BBBB OOO X X
 ************************************************************/

// ************************************************************
class BBox
// ************************************************************
// ************************************************************
{
	double xmin, xmax;
	double ymin, ymax;
	double zmin, zmax;
	static int hit;

	BBox() {
		xmin = 1e50;
		xmax = -1e50;
		ymin = 1e50;
		ymax = -1e50;
		zmin = 1e50;
		zmax = -1e50;
	};
	// ************************************************************
} // end class BBox
// ************************************************************

/************************************************************
 * PPPP AAA RRRR TTTTT P P A A R R T PPPP AAAAA RRRR T P A A R R T P A A R R T
 ************************************************************/

// ************************************************************
class Part
// ************************************************************
// ************************************************************
{
	Prim[] obj;
	Prim[] bound;
	boolean visible;

	// ************************************************************
	void makeBound()
	// ************************************************************
	{
		BBox b = new BBox();

		bound(b);
		bound = new Prim[1];
		bound[0] = new Sphere(new P((b.xmin + b.xmax) / 2, (b.ymin + b.ymax) / 2, (b.zmin + b.zmax) / 2),
				0.5 * Math.sqrt((b.xmin - b.xmax) * (b.xmin - b.xmax) + (b.ymin - b.ymax) * (b.ymin - b.ymax) + (b.zmin - b.zmax)
						* (b.zmin - b.zmax)));
	}

	// ************************************************************
	void bound(BBox bb)
	// ************************************************************
	{
		for (int i = 0; i < obj.length; ++i)
			obj[i].bound(bb);
	}

	// ************************************************************
	void transform(M m)
	// ************************************************************
	{
		for (int i = 0; i < obj.length; ++i)
			obj[i].transform(m);
		for (int i = 0; i < bound.length; ++i)
			bound[i].transform(m);
	}

	// ************************************************************
	Prim closestIntersection(Ray r) // part.closestIntersection
	// ************************************************************
	{
		if (!visible)
			return null;
		boolean dhit = false;
		double closestDistance = 1e100;
		Prim closestPrim = null;

		if (bound.length == 0)
			dhit = true;
		for (int i = 0; i < bound.length; ++i) {
			if (bound[i].anyIntersection(r) != null) {
				dhit = true;
				++BBox.hit;
			}
		}
		if (dhit) {
			for (int i = 0; i < obj.length; ++i) {
				Prim p = obj[i].closestIntersection(r);
				if (p != null) {
					if (p.depth < closestDistance) {
						closestDistance = p.depth;
						closestPrim = p;
					}
				}
			}
		}
		return closestPrim;
	}

	// ************************************************************
	Prim anyIntersection(Ray r) // part.anyIntersection
	// ************************************************************
	{
		if (!visible)
			return null;
		boolean dhit = false;
		if (bound.length == 0)
			dhit = true;
		for (int i = 0; i < bound.length; ++i) {
			if (bound[i].anyIntersection(r) != null) {
				dhit = true;
				++BBox.hit;
			}
		}

		if (dhit) {
			for (int i = 0; i < obj.length; ++i) {
				Prim p = obj[i].anyIntersection(r);
				if (p != null)
					return p;
			}
		}
		return null;
	};

	// ************************************************************
	void allIntersections(Ray ray, PrimList pl)
	// ************************************************************
	{
		if (!visible)
			return;
		boolean bbhit = false;
		if (bound.length == 0)
			bbhit = true;
		for (int i = 0; i < bound.length; ++i) {
			if (bound[i].anyIntersection(ray) != null)
				bbhit = true;
		}
		if (bbhit) {
			for (int s = 0; s < obj.length; ++s)
				obj[s].allIntersections(ray, pl);
		}
	}

	// ************************************************************
	Part()
	// ************************************************************
	{
		obj = new Prim[0];
		bound = new Prim[0];
		visible = true;
	};
	// ************************************************************
}; // end class Part
// ************************************************************

/************************************************************
 * III SSS SSS RRRR EEEEE AAA DDDD EEEEE RRRR I S S R R E A A D D E R R I SSS SSS RRRR EEE AAAAA D D EEE RRRR I S S R R
 * E A A D D E R R III SSS SSS ___ R R EEEEE A A DDDD EEEEE R R
 ************************************************************/

// ************************************************************
class ISS_Reader
// ***********************************************************
// ************************************************************
{
	static InputStream in;
	static int linenumber;
	static int lineinc;
	static String[] saved_tokens;
	static int token_index;
	static boolean tflag;

	// ************************************************************
	static String readLine(FileInputStream f) throws Exception
	// ************************************************************
	{
		int c;
		String line = "";
		c = f.read();
		while (c != '\n' && c != -1) {
			line += ((char) c);
			c = f.read();
		}
		return line;
	}

	// ************************************************************
	static String readLine() throws Exception
	// ************************************************************
	{
		String line = "";
		return line;
	}

	// ************************************************************
	static String readString() throws Exception
	// ************************************************************
	{
		if (saved_tokens != null) {
			linenumber++;
			return (saved_tokens[token_index++]);
		}

		int c = 0;
		String val = "";

		linenumber += lineinc;
		lineinc = 0;
		c = in.read();
		while (c <= 32 || c == ',') // commma considered white space
		{
			if (c == -1)
				break;
			if (c == '\n')
				++linenumber;
			c = in.read();
		}
		if (c == '{') // comments
		{
			c = in.read();
			while (c != '}')
				c = in.read();
			return readString();
		}

		while (c > 32 && c != ',') {
			val += (char) c;
			c = in.read();
		}
		if (c == '\n')
			lineinc = 1;

		// System.out.println ( "  read " + val + " at line " + linenumber ) ;

		return val;
	};

	// ************************************************************
	static double readDouble() throws Exception
	// ************************************************************
	{
		double val = 0;
		String s = "0";
		try {
			s = readString();
			val = Double.parseDouble(s);
		} catch (Exception e) {
			System.err.println("number format exception (" + s + ") at line " + linenumber);

		}
		return val;
	};

	// ************************************************************
	static int readInteger() throws Exception
	// ************************************************************
	{
		return (Integer.parseInt(readString()));
	};

	// ************************************************************
	void expect(String key) throws Exception
	// ************************************************************
	{
		String got = readString();
		expect(got, key);
	};

	// ************************************************************
	void expect(String got, String key) throws Exception
	// ************************************************************
	{
		if (!got.equals(key)) {
			System.err.println("parser expected " + key + " but got " + got + " on line " + linenumber);
		}
	};

	// ************************************************************
	Prim readSphere() throws Exception
	// ************************************************************
	{
		double x, y, z;
		expect("radius");
		double r = readDouble();
		expect("center");
		x = readDouble();
		y = readDouble();
		z = readDouble();
		P c = new P(x, y, z);
		expect("endsphere");
		return new Sphere(c, r);
	}

	// ************************************************************
	Prim readCylinder() throws Exception
	// ************************************************************
	{
		double x, y, z;
		expect("radius");
		double r = readDouble();
		expect("center");
		x = readDouble();
		y = readDouble();
		z = readDouble();
		P a1 = new P(x, y, z);
		expect("center");
		x = readDouble();
		y = readDouble();
		z = readDouble();
		P a2 = new P(x, y, z);
		expect("endcylinder");
		return new Cylinder(a1, a2, r);
	}

	// ************************************************************
	Prim readPolygon() throws Exception
	// ************************************************************
	{
		P[] vtx = new P[100];
		int vc = 0;
		String vs = readString();
		while (vs.equals("vertex")) {
			double x = readDouble();
			double y = readDouble();
			double z = readDouble();
			P pt = new P(x, y, z);
			vtx[vc++] = pt;
			vs = readString();
		}
		expect(vs, "endpolygon");

		Polygon p = new Polygon(vtx, vc);
		return p;
	};

	// ************************************************************
	Prim readPrim(String what) throws Exception
	// ************************************************************
	{
		Prim p = null;
		if (what.equals("polygon"))
			p = readPolygon();
		else if (what.equals("cylinder"))
			p = readCylinder();
		else if (what.equals("sphere"))
			p = readSphere();
		else {
			expect(what, "polygon, cylinder or sphere");
		}
		return p;
	}

	// ************************************************************
	Part readShape() throws Exception
	// ************************************************************
	{
		Prim[] pt = new Prim[1000];
		Part sol = new Part();
		int pc = 0;
		String key;
		String name;
		// expect ( "shape" ) ;

		name = readString();
		key = readString();
		while (key.equals("cylinder") || key.equals("sphere") || key.equals("polygon")) {
			pt[pc++] = readPrim(key);
			key = readString();
		}

		sol.obj = new Prim[pc];
		for (int i = 0; i < pc; ++i)
			sol.obj[i] = pt[i];
		expect(key, "endshape");
		return sol;
	};

	// ************************************************************
	Structure readStructure() throws Exception
	// ************************************************************
	{
		String key;
		Structure mod = new Structure();
		Part[] sa = new Part[100];

		// expect ( "structure" ) ;
		key = readString();
		String name = key;
		// System.out.println ( "reading structure " + key ) ;

		expect("parts");
		key = readString();
		int si = 0;
		while (key.equals("shape")) {
			sa[si++] = readShape();
			key = readString();
		}
		mod.solid = new Part[si];
		for (int i = 0; i < si; ++i) {
			mod.solid[i] = sa[i];
			mod.solid[i].makeBound();
		}
		expect(key, "endparts");

		expect("children");
		int childCount = 0;
		Structure[] tc = new Structure[100];
		key = readString();
		while (key.equals("structure")) {
			tc[childCount++] = readStructure();
			key = readString();
		}

		mod.child = new Structure[childCount];
		for (int i = 0; i < childCount; ++i)
			mod.child[i] = tc[i];
		expect(key, "endchildren");
		// System.out.println ( name ) ;

		expect("endstructure");
		mod.makeBound();
		return mod;
	}

	// ************************************************************
	Structure readModel()
	// ************************************************************
	{
		Structure mod = null;

		try {
			expect("model");
			ISSVis.model_name = readString();
			expect("structure");
			mod = readStructure();
			expect("endmodel");
		} catch (Exception e) {
			System.out.println("ERROR: reading input file at line " + linenumber);
			e.printStackTrace();
			System.exit(666);
		}
		fix_longerons(mod);
		return mod;
	};

	// ************************************************************
	void fix_longeron(Structure m, int side, int saw, int[] offsets, int index)
	// ************************************************************
	{

		// SAW model
		// 0 bottom arm
		// ...
		// 1 top arm
		// ...
		// 2 blanket 1
		// 0 polygon
		// 3 blanket 2
		// 0 polygon
		// 4 longerons
		// 0 cylinder
		// 1 cylinder
		// 2 cylinder
		// 3 cylinder
		// 5 bottom cap
		// 0 cylinder
		// 6 top cap
		// 0 cylinder
		Cylinder c = (Cylinder) m.child[side].child[saw].solid[5].obj[0];
		P axis = c.a1;

		Cylinder longeron;

		for (int i = 0; i < 4; i++) {
			longeron = (Cylinder) m.child[side].child[saw].solid[4].obj[i];

			longeron.a1.y = axis.y - offsets[index + 2 * i];
			longeron.a1.z = axis.z - offsets[index + 2 * i + 1];
			longeron.original_a1.y = axis.y - offsets[index + 2 * i];
			longeron.original_a1.z = axis.z - offsets[index + 2 * i + 1];

			longeron.a2.y = longeron.a1.y;
			longeron.a2.z = longeron.a1.z;
			longeron.original_a2.y = longeron.a1.y;
			longeron.original_a2.z = longeron.a1.z;
		}
	}

	// ************************************************************
	void fix_longerons(Structure m)
	// ************************************************************
	{
		// From NASA memo, y z offsets of longerons from rotation axis
		//
		// order of table "offsets"
		// 1A ( y,z, y,z, y,z, y,z ) 1B ( ... ) ... 3B ( ... ) 4B ( ... )
		//
		int[] offsets = new int[] { -181, -502, 580, -367, 444, 394, -316, 258,

		386, -637, -375, -503, -241, 258, 520, 124,

		183, -622, -578, -489, -446, 272, 316, 139,

		-322, -542, 439, -407, 305, 354, -456, 219,

		296, -607, -464, -473, -330, 288, 431, 154,

		-243, -558, 517, -422, 381, 339, -379, 202,

		-248, -562, 513, -430, 380, 332, -381, 199,

		246, -566, -515, -434, -383, 327, 378, 195 };

		fix_longeron(m, 0, 0, offsets, 0);
		fix_longeron(m, 1, 0, offsets, 8);
		fix_longeron(m, 0, 1, offsets, 16);
		fix_longeron(m, 1, 1, offsets, 24);
		fix_longeron(m, 0, 2, offsets, 32);
		fix_longeron(m, 1, 2, offsets, 40);
		fix_longeron(m, 0, 3, offsets, 48);
		fix_longeron(m, 1, 3, offsets, 56);
	}

	// ************************************************************
	ISS_Reader(String[] tokes)
	// ************************************************************
	{
		saved_tokens = tokes;
		token_index = 0;
		tflag = false;
		linenumber = 1;
		lineinc = 0;
	}

	// ************************************************************
	ISS_Reader(String name)
	// ************************************************************
	{
		try {
			System.out.println("reading " + name);
//			in = new FileInputStream(name);
			in = this.getClass().getClassLoader().getResourceAsStream(name);
			linenumber = 1;
			lineinc = 0;
		} catch (Exception e) {
			System.out.println("ERROR: Unable to open file " + name + " for reading.");
			e.printStackTrace();
			System.exit(666);
		}
	}
	// ************************************************************
}; // end class IIS_Reader
// ************************************************************

/************************************************************
 * SSS TTTTT RRRR U U CCC TTTTT U U RRRR EEEEE S T R R U U C T U U R R E SSS T RRRR U U C T U U RRRR EEE S T R R U U C T
 * U U R R E SSS T R R UUU CCC T UUU R R EEEEE
 ************************************************************/

// ************************************************************
class Structure
// ************************************************************
// ************************************************************
{
	Part[] solid;
	Structure[] child;
	Prim[] bb; // bounding boxes, (may also be spheres)
	boolean visible;
	M transform;

	static Prim last_prim;

	// ************************************************************
	void makeBound()
	// ************************************************************
	{
		BBox b = new BBox();

		bound(b);
		// System.out.println ( "bbox " + b.xmin + " " + b.xmax + " " + b.ymin +
		// " " + b.ymax + " " + b.zmin + " " + b.zmax ) ;
		bb = new Prim[1];
		bb[0] = new Sphere(new P((b.xmin + b.xmax) / 2, (b.ymin + b.ymax) / 2, (b.zmin + b.zmax) / 2), 0.5 * Math.sqrt((b.xmin - b.xmax)
				* (b.xmin - b.xmax) + (b.ymin - b.ymax) * (b.ymin - b.ymax) + (b.zmin - b.zmax) * (b.zmin - b.zmax)));
	}

	// ************************************************************
	void bound(BBox b)
	// ************************************************************
	{
		for (int i = 0; i < solid.length; ++i)
			// parts
			solid[i].bound(b);
	}

	// ************************************************************
	void transform(M m)
	// ************************************************************
	{
		M new_m = new M();
		transform.compose_pre(m, new_m);

		for (int i = 0; i < solid.length; ++i)
			solid[i].transform(new_m);
		for (int i = 0; i < child.length; ++i)
			child[i].transform(new_m);
		for (int i = 0; i < bb.length; ++i)
			bb[i].transform(new_m);
	}

	// ************************************************************
	void transform()
	// ************************************************************
	{
		M new_m = new M();
		transform(new_m);
	}

	// ************************************************************
	Prim closestIntersection(Ray ray) // model.closestIntersection
	// ************************************************************
	{
		++ISSVis.closest_cast;
		double closestDistance = 1e100;
		Prim closestPrim = null;
		if (!visible)
			return null;
		Prim p = null;
		;

		boolean bbhit = false;
		for (int i = 0; i < bb.length; i++) {
			if (null != bb[i].anyIntersection(ray)) {
				++BBox.hit;
				bbhit = true;
			}
		}

		if (bbhit) {
			p = null;
			for (int s = 0; s < solid.length; ++s) {
				p = solid[s].closestIntersection(ray);
				if (p != null) {
					if (p.depth < closestDistance) {
						closestDistance = p.depth;
						closestPrim = p;
					}
				}
			}
		}

		// cast rays for children always
		for (int c = 0; c < child.length; c++) {
			p = child[c].closestIntersection(ray);
			if (p != null) {
				if (p.depth < closestDistance) {
					closestDistance = p.depth;
					closestPrim = p;
				}
			}
		}
		if (closestPrim != null)
			++ISSVis.closest_hits;
		return closestPrim;
	}

	static Prim hint = null;

	// ************************************************************
	Prim anyIntersectionHint(Ray ray) // model.anyIntersection
	// ************************************************************
	{
		// I questioned whether this should be any different than
		// anyIntersection, since it may lose some coherency which
		// would make subdivision less efficient.
		// Without the hint, anyIntersection always returns the first
		// intersected prim from a fixed order.

		Prim hit = null;

		++ISSVis.any_cast;
		if (hint != null) {
			hit = hint.anyIntersection(ray);
		}
		if (hit == null) {
			++ISSVis.multi_cast;
			hit = anyIntersection(ray);
			if (hit != null) {
				hint = hit;
				++ISSVis.multi_hits;
			}
		} else
			++ISSVis.one_hits;
		return hit;
	}

	// ************************************************************
	void allIntersections(Ray ray, PrimList pl)
	// ************************************************************
	{
		ISSVis.all_cast++;
		if (!visible)
			return;
		boolean bbhit = false;
		if (bb.length == 0)
			bbhit = true;
		for (int i = 0; i < bb.length; ++i) {
			if (bb[i].anyIntersection(ray) != null)
				bbhit = true;
		}
		if (bbhit) {
			for (int s = 0; s < solid.length; ++s)
				solid[s].allIntersections(ray, pl);
		}
		for (int c = 0; c < child.length; c++)
			child[c].allIntersections(ray, pl);
		ISSVis.all_hits += pl.s;
	}

	// ************************************************************
	Prim anyIntersection(Ray ray) // model.anyIntersection
	// ************************************************************
	{
		if (!visible)
			return null;
		Prim p = null;
		;
		/* if ( last_prim != null ) */
		/* p = last_prim.anyIntersection ( ray ) ; */
		if (p != null)
			return p;

		boolean bbhit = false;
		for (int i = 0; i < bb.length; i++) {
			if (null != bb[i].anyIntersection(ray)) {
				++BBox.hit;
				bbhit = true;
				break;
			}
		}

		if (bbhit) {
			p = null;
			for (int s = 0; s < solid.length; ++s) {
				p = solid[s].anyIntersection(ray);
				if (p != null) {
					last_prim = p;
					return p;
				}
			}
		}

		// cast rays for children always
		for (int c = 0; c < child.length; c++) {
			p = child[c].anyIntersection(ray);
			if (p != null) {
				last_prim = p;
				return p;
			}
		}
		return null;
	};

	// ************************************************************
	Structure()
	// ************************************************************
	{
		last_prim = null;
		solid = new Part[0];
		child = new Structure[0];
		transform = new M();
		bb = new Prim[0];
		visible = true;
	};
	// ************************************************************
}; // end of class Structure
// ************************************************************

/************************************************************
 * PPPP RRRR III M M L III SSS TTTTT P P R R I MM MM L I S T PPPP RRRR I M M M L I SSS T P R R I M M L I S T P R R III M
 * M LLLLL III SSS T
 ************************************************************/
// ************************************************************
class PrimList
// ************************************************************
{
	Prim[] prim;
	boolean[] valid;
	int s;

	// ************************************************************
	void copy(PrimList original)
	// ************************************************************
	{
		s = original.s;
		for (int i = 0; i < s; ++i) {
			valid[i] = true;
			prim[i] = original.prim[i];
		}
	}

	// ************************************************************
	void reset()
	// ************************************************************
	{
		for (int i = 0; i < s; ++i)
			valid[i] = true;
		s = 0;
	}

	// ************************************************************
	PrimList()
	// ************************************************************
	{
		prim = new Prim[600];
		valid = new boolean[600];
		s = 0;
	}
	// ************************************************************
} // end of class PrimList
// ************************************************************

/************************************************************
 * PPPP RRRR III M M P P R R I MM MM PPPP RRRR I M M M P R R I M M P R R III M M
 ************************************************************/

// ************************************************************
abstract class Prim
// ************************************************************
// ************************************************************
{
	boolean visible;
	short red;
	short green;
	short blue;
	short basered;
	short basegreen;
	short baseblue;
	double depth;
	int order;
	static int next_order = 0;
	V normal;

	abstract Prim closestIntersection(Ray r);

	abstract Prim anyIntersection(Ray r);

	abstract void transform(M old);

	abstract void bound(BBox b);

	abstract void print();

	// ************************************************************
	void print(String s)
	// ************************************************************
	{
		print();
		System.out.print(s);
	}

	// ************************************************************
	void print(String pre, String post)
	// ************************************************************
	{
		System.out.print(pre);
		print();
		System.out.print(post);
	}

	// ************************************************************
	void allIntersections(Ray ray, PrimList p) {
		Prim x = anyIntersection(ray);
		if (x != null) {
			p.valid[p.s] = true;
			p.prim[p.s++] = x;
		}
	}

	// ************************************************************
	Prim() {
		order = next_order++;
	}
	// ************************************************************
}; // end of class Prim
// ************************************************************

/************************************************************
 * RRRR AAA Y Y R R A A Y Y RRRR AAAAA Y R R A A Y R R A A Y
 ************************************************************/

// ************************************************************
class Ray
// ************************************************************
// ************************************************************
{
	P o;
	V d;

	void print(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post);
	}

	void print(String s) {
		print();
		System.out.print(s);
	}

	void println(String pre, String post) {
		System.out.print(pre);
		print();
		System.out.print(post + "\n");
	}

	void println(String s) {
		print();
		System.out.print(s + "\n");
	}

	void print() {
		System.out.print("Ray o: ");
		o.print();
		System.out.print(" d: ");
		d.print();
	};

	void println() {
		print();
		System.out.println();
	};

	Ray(P origin, V direction) {
		o = new P(origin);
		d = new V(direction);
	};

	Ray() {
		o = new P();
		d = new V();
	};

	// ************************************************************
}; // end of class Ray
// ************************************************************

/************************************************************
 * SSS PPPP H H EEEEE RRRR EEEEE S P P H H E R R E SSS PPPP HHHHH EEE RRRR EEE S P H H E R R E SSS P H H EEEEE R R EEEEE
 ************************************************************/

// ************************************************************
class Sphere extends Prim
// ************************************************************
// ************************************************************
{
	double rad;
	P cen, original_cen;

	void bound(BBox b) {
		if (cen.x + rad > b.xmax)
			b.xmax = cen.x + rad;
		if (cen.y + rad > b.ymax)
			b.ymax = cen.y + rad;
		if (cen.z + rad > b.zmax)
			b.zmax = cen.z + rad;
		if (cen.x - rad < b.xmin)
			b.xmin = cen.x - rad;
		if (cen.y - rad < b.ymin)
			b.ymin = cen.y - rad;
		if (cen.z - rad < b.zmin)
			b.zmin = cen.z - rad;
	}

	void transform(M m) {
		m.transform(original_cen, cen);
	};

	void print() {
		System.out.print("Sphere c: ");
		cen.print();
		System.out.print(" r: " + rad);
	};

	void println() {
		print();
		System.out.println();
	};

	Prim closestIntersection(Ray ray) // sphere.closestIntersection
	{
		// we don't have spheres in the model, so this is never called
		depth = ray.d.dotp(cen);
		return anyIntersection(ray); // we only use them for bounding
	};

	// ************************************************************
	Prim anyIntersection(Ray ray) // sphere.anyIntersection
	// ************************************************************
	{
		double a = cen.x - ray.o.x;
		double b = cen.y - ray.o.y;
		double c = cen.z - ray.o.z;

		double B = 2 * (-a * ray.d.x - b * ray.d.y - c * ray.d.z);

		double disc = B * B - 4 * (a * a + b * b + c * c - rad * rad);

		++ISSVis.one_cast;
		if (disc < 0.0)
			return null;
		// values of t for intersection are -B-sqrt()/2A and -B+sqrt()/2A
		// the + one is farther away. Use it since we don't care about normal
		// of first visible intersection
		double t = 0.5 * Math.sqrt(disc) + a * ray.d.x + b * ray.d.y + c * ray.d.z;
		if (t <= 0)
			return null;
		return (Prim) this; // we hit it
	};

	// ************************************************************

	Sphere() {
		cen = new P();
		original_cen = new P();
		basered = 200;
		basegreen = 200;
		baseblue = 200;
		visible = true;
		normal = new V();
	}

	Sphere(P c, double r) {
		cen = new P(c);
		original_cen = new P(c);
		rad = r;
		basered = 200;
		basegreen = 200;
		baseblue = 200;
		visible = true;
		normal = new V();
	}

	// ************************************************************
}; // end of class sphere
// ************************************************************

